/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.mail.internet;

import javax.mail.*;
import javax.activation.*;
import java.util.*;
import java.io.*;
import com.sun.mail.util.LineOutputStream;
import com.sun.mail.util.LineInputStream;
import com.sun.mail.util.ASCIIUtility;
import com.sun.mail.util.PropUtil;

/**
 * The MimeMultipart class is an implementation of the abstract Multipart
 * class that uses MIME conventions for the multipart data.
 * <p>
 * 
 * A MimeMultipart is obtained from a MimePart whose primary type is "multipart"
 * (by invoking the part's <code>getContent()</code> method) or it can be
 * created by a client as part of creating a new MimeMessage.
 * <p>
 * 
 * The default multipart subtype is "mixed". The other multipart subtypes, such
 * as "alternative", "related", and so on, can be implemented as subclasses of
 * MimeMultipart with additional methods to implement the additional semantics
 * of that type of multipart content. The intent is that service providers, mail
 * JavaBean writers and mail clients will write many such subclasses and their
 * Command Beans, and will install them into the JavaBeans Activation Framework,
 * so that any JavaMail implementation and its clients can transparently find
 * and use these classes. Thus, a MIME multipart handler is treated just like
 * any other type handler, thereby decoupling the process of providing multipart
 * handlers from the JavaMail API. Lacking these additional MimeMultipart
 * subclasses, all subtypes of MIME multipart data appear as MimeMultipart
 * objects.
 * <p>
 * 
 * An application can directly construct a MIME multipart object of any subtype
 * by using the <code>MimeMultipart(String subtype)</code> constructor. For
 * example, to create a "multipart/alternative" object, use
 * <code>new MimeMultipart("alternative")</code>.
 * <p>
 * 
 * The <code>mail.mime.multipart.ignoremissingendboundary</code> property may be
 * set to <code>false</code> to cause a <code>MessagingException</code> to be
 * thrown if the multipart data does not end with the required end boundary
 * line. If this property is set to <code>true</code> or not set, missing end
 * boundaries are not considered an error and the final body part ends at the
 * end of the data.
 * <p>
 * 
 * The <code>mail.mime.multipart.ignoremissingboundaryparameter</code> System
 * property may be set to <code>false</code> to cause a
 * <code>MessagingException</code> to be thrown if the Content-Type of the
 * MimeMultipart does not include a <code>boundary</code> parameter. If this
 * property is set to <code>true</code> or not set, the multipart parsing code
 * will look for a line that looks like a bounary line and use that as the
 * boundary separating the parts.
 * <p>
 * 
 * The current implementation also supports the following properties:
 * <p>
 * 
 * The <code>mail.mime.multipart.ignoreexistingboundaryparameter</code> System
 * property may be set to <code>true</code> to cause any boundary to be ignored
 * and instead search for a boundary line in the message as with
 * <code>mail.mime.multipart.ignoremissingboundaryparameter</code>.
 * <p>
 * 
 * Normally, when writing out a MimeMultipart that contains no body parts, or
 * when trying to parse a multipart message with no body parts, a
 * <code>MessagingException</code> is thrown. The MIME spec does not allow
 * multipart content with no body parts. The
 * <code>mail.mime.multipart.allowempty</code> System property may be set to
 * <code>true</code> to override this behavior. When writing out such a
 * MimeMultipart, a single empty part will be included. When reading such a
 * multipart, a MimeMultipart will be created with no body parts.
 * 
 * @author John Mani
 * @author Bill Shannon
 * @author Max Spivak
 */

public class MimeMultipart extends Multipart {

	/**
	 * The DataSource supplying our InputStream.
	 */
	protected DataSource ds = null;

	/**
	 * Have we parsed the data from our InputStream yet?
	 * Defaults to true; set to false when our constructor is
	 * given a DataSource with an InputStream that we need to
	 * parse.
	 */
	protected boolean parsed = true;

	/**
	 * Have we seen the final bounary line?
	 */
	private boolean complete = true;

	/**
	 * The MIME multipart preamble text, the text that
	 * occurs before the first boundary line.
	 */
	private String preamble = null;

	/**
	 * Flags to control parsing, initialized from System properties
	 * in the parse() method.
	 */
	private boolean ignoreMissingEndBoundary = true;
	private boolean ignoreMissingBoundaryParameter = true;
	private boolean ignoreExistingBoundaryParameter = false;
	private boolean allowEmpty = false;
	private boolean bmparse = true;

	/**
	 * Default constructor. An empty MimeMultipart object
	 * is created. Its content type is set to "multipart/mixed".
	 * A unique boundary string is generated and this string is
	 * setup as the "boundary" parameter for the <code>contentType</code> field.
	 * <p>
	 * 
	 * MimeBodyParts may be added later.
	 */
	public MimeMultipart() {
		this("mixed");
	}

	/**
	 * Construct a MimeMultipart object of the given subtype.
	 * A unique boundary string is generated and this string is
	 * setup as the "boundary" parameter for the <code>contentType</code> field.
	 * <p>
	 * 
	 * MimeBodyParts may be added later.
	 */
	public MimeMultipart(String subtype) {
		super();
		/*
		 * Compute a boundary string.
		 */
		String boundary = UniqueValue.getUniqueBoundaryValue();
		ContentType cType = new ContentType("multipart", subtype, null);
		cType.setParameter("boundary", boundary);
		contentType = cType.toString();
	}

	/**
	 * Constructs a MimeMultipart object and its bodyparts from the
	 * given DataSource.
	 * <p>
	 * 
	 * This constructor handles as a special case the situation where the given
	 * DataSource is a MultipartDataSource object. In this case, this method
	 * just invokes the superclass (i.e., Multipart) constructor that takes a
	 * MultipartDataSource object.
	 * <p>
	 * 
	 * Otherwise, the DataSource is assumed to provide a MIME multipart byte
	 * stream. The <code>parsed</code> flag is set to false. When the data for
	 * the body parts are needed, the parser extracts the "boundary" parameter
	 * from the content type of this DataSource, skips the 'preamble' and reads
	 * bytes till the terminating boundary and creates MimeBodyParts for each
	 * part of the stream.
	 * 
	 * @param ds
	 *            DataSource, can be a MultipartDataSource
	 */
	public MimeMultipart(DataSource ds) throws MessagingException {
		super();

		if (ds instanceof MessageAware) {
			MessageContext mc = ((MessageAware) ds).getMessageContext();
			setParent(mc.getPart());
		}

		if (ds instanceof MultipartDataSource) {
			// ask super to do this for us.
			setMultipartDataSource((MultipartDataSource) ds);
			return;
		}

		// 'ds' was not a MultipartDataSource, we have
		// to parse this ourself.
		parsed = false;
		this.ds = ds;
		contentType = ds.getContentType();
	}

	/**
	 * Set the subtype. This method should be invoked only on a new
	 * MimeMultipart object created by the client. The default subtype
	 * of such a multipart object is "mixed".
	 * <p>
	 * 
	 * @param subtype
	 *            Subtype
	 */
	public synchronized void setSubType(String subtype) throws MessagingException {
		ContentType cType = new ContentType(contentType);
		cType.setSubType(subtype);
		contentType = cType.toString();
	}

	/**
	 * Return the number of enclosed BodyPart objects.
	 * 
	 * @return number of parts
	 */
	public synchronized int getCount() throws MessagingException {
		parse();
		return super.getCount();
	}

	/**
	 * Get the specified BodyPart. BodyParts are numbered starting at 0.
	 * 
	 * @param index
	 *            the index of the desired BodyPart
	 * @return the Part
	 * @exception MessagingException
	 *                if no such BodyPart exists
	 */
	public synchronized BodyPart getBodyPart(int index) throws MessagingException {
		parse();
		return super.getBodyPart(index);
	}

	/**
	 * Get the MimeBodyPart referred to by the given ContentID (CID).
	 * Returns null if the part is not found.
	 * 
	 * @param CID
	 *            the ContentID of the desired part
	 * @return the Part
	 */
	public synchronized BodyPart getBodyPart(String CID) throws MessagingException {
		parse();

		int count = getCount();
		for (int i = 0; i < count; i++) {
			MimeBodyPart part = (MimeBodyPart) getBodyPart(i);
			String s = part.getContentID();
			if (s != null && s.equals(CID))
				return part;
		}
		return null;
	}

	/**
	 * Remove the specified part from the multipart message.
	 * Shifts all the parts after the removed part down one.
	 * 
	 * @param part
	 *            The part to remove
	 * @return true if part removed, false otherwise
	 * @exception MessagingException
	 *                if no such Part exists
	 * @exception IllegalWriteException
	 *                if the underlying
	 *                implementation does not support modification
	 *                of existing values
	 */
	public boolean removeBodyPart(BodyPart part) throws MessagingException {
		parse();
		return super.removeBodyPart(part);
	}

	/**
	 * Remove the part at specified location (starting from 0).
	 * Shifts all the parts after the removed part down one.
	 * 
	 * @param index
	 *            Index of the part to remove
	 * @exception MessagingException
	 * @exception IndexOutOfBoundsException
	 *                if the given index
	 *                is out of range.
	 * @exception IllegalWriteException
	 *                if the underlying
	 *                implementation does not support modification
	 *                of existing values
	 */
	public void removeBodyPart(int index) throws MessagingException {
		parse();
		super.removeBodyPart(index);
	}

	/**
	 * Adds a Part to the multipart. The BodyPart is appended to
	 * the list of existing Parts.
	 * 
	 * @param part
	 *            The Part to be appended
	 * @exception MessagingException
	 * @exception IllegalWriteException
	 *                if the underlying
	 *                implementation does not support modification
	 *                of existing values
	 */
	public synchronized void addBodyPart(BodyPart part) throws MessagingException {
		parse();
		super.addBodyPart(part);
	}

	/**
	 * Adds a BodyPart at position <code>index</code>.
	 * If <code>index</code> is not the last one in the list,
	 * the subsequent parts are shifted up. If <code>index</code> is larger than
	 * the number of parts present, the
	 * BodyPart is appended to the end.
	 * 
	 * @param part
	 *            The BodyPart to be inserted
	 * @param index
	 *            Location where to insert the part
	 * @exception MessagingException
	 * @exception IllegalWriteException
	 *                if the underlying
	 *                implementation does not support modification
	 *                of existing values
	 */
	public synchronized void addBodyPart(BodyPart part, int index) throws MessagingException {
		parse();
		super.addBodyPart(part, index);
	}

	/**
	 * Return true if the final boundary line for this
	 * multipart was seen. When parsing multipart content,
	 * this class will (by default) terminate parsing with
	 * no error if the end of input is reached before seeing
	 * the final multipart boundary line. In such a case,
	 * this method will return false. (If the System property
	 * "mail.mime.multipart.ignoremissingendboundary" is set to
	 * false, parsing such a message will instead throw a
	 * MessagingException.)
	 * 
	 * @return true if the final boundary line was seen
	 * @since JavaMail 1.4
	 */
	public synchronized boolean isComplete() throws MessagingException {
		parse();
		return complete;
	}

	/**
	 * Get the preamble text, if any, that appears before the
	 * first body part of this multipart. Some protocols,
	 * such as IMAP, will not allow access to the preamble text.
	 * 
	 * @return the preamble text, or null if no preamble
	 * @since JavaMail 1.4
	 */
	public synchronized String getPreamble() throws MessagingException {
		parse();
		return preamble;
	}

	/**
	 * Set the preamble text to be included before the first
	 * body part. Applications should generally not include
	 * any preamble text. In some cases it may be helpful to
	 * include preamble text with instructions for users of
	 * pre-MIME software. The preamble text should be complete
	 * lines, including newlines.
	 * 
	 * @param preamble
	 *            the preamble text
	 * @since JavaMail 1.4
	 */
	public synchronized void setPreamble(String preamble) throws MessagingException {
		this.preamble = preamble;
	}

	/**
	 * Update headers. The default implementation here just
	 * calls the <code>updateHeaders</code> method on each of its
	 * children BodyParts.
	 * <p>
	 * 
	 * Note that the boundary parameter is already set up when a new and empty
	 * MimeMultipart object is created.
	 * <p>
	 * 
	 * This method is called when the <code>saveChanges</code> method is invoked
	 * on the Message object containing this Multipart. This is typically done
	 * as part of the Message send process, however note that a client is free
	 * to call it any number of times. So if the header updating process is
	 * expensive for a specific MimeMultipart subclass, then it might itself
	 * want to track whether its internal state actually did change, and do the
	 * header updating only if necessary.
	 */
	protected synchronized void updateHeaders() throws MessagingException {
		parse();
		for (int i = 0; i < parts.size(); i++)
			((MimeBodyPart) parts.elementAt(i)).updateHeaders();
	}

	/**
	 * Iterates through all the parts and outputs each MIME part
	 * separated by a boundary.
	 */
	public synchronized void writeTo(OutputStream os) throws IOException, MessagingException {
		parse();

		String boundary = "--" + (new ContentType(contentType)).getParameter("boundary");
		LineOutputStream los = new LineOutputStream(os);

		// if there's a preamble, write it out
		if (preamble != null) {
			byte[] pb = ASCIIUtility.getBytes(preamble);
			los.write(pb);
			// make sure it ends with a newline
			if (pb.length > 0 && !(pb[pb.length - 1] == '\r' || pb[pb.length - 1] == '\n')) {
				los.writeln();
			}
			// XXX - could force a blank line before start boundary
		}

		if (parts.size() == 0) {
			// have to read the property every time because parse won't
			// read it if the message was *not* constructed from a stream.
			// default to false
			allowEmpty = PropUtil.getBooleanSystemProperty("mail.mime.multipart.allowempty", false);
			if (allowEmpty) {
				// write out a single empty body part
				los.writeln(boundary); // put out boundary
				los.writeln(); // put out empty line
			} else {
				throw new MessagingException("Empty multipart: " + contentType);
			}
		} else {
			for (int i = 0; i < parts.size(); i++) {
				los.writeln(boundary); // put out boundary
				((MimeBodyPart) parts.elementAt(i)).writeTo(os);
				los.writeln(); // put out empty line
			}
		}

		// put out last boundary
		los.writeln(boundary + "--");
	}

	/**
	 * Parse the InputStream from our DataSource, constructing the
	 * appropriate MimeBodyParts. The <code>parsed</code> flag is
	 * set to true, and if true on entry nothing is done. This
	 * method is called by all other methods that need data for
	 * the body parts, to make sure the data has been parsed.
	 * 
	 * @since JavaMail 1.2
	 */
	protected synchronized void parse() throws MessagingException {
		if (parsed)
			return;

		// read properties that control parsing

		// default to true
		ignoreMissingEndBoundary = PropUtil.getBooleanSystemProperty("mail.mime.multipart.ignoremissingendboundary", true);
		// default to true
		ignoreMissingBoundaryParameter = PropUtil.getBooleanSystemProperty("mail.mime.multipart.ignoremissingboundaryparameter", true);
		// default to false
		ignoreExistingBoundaryParameter = PropUtil.getBooleanSystemProperty("mail.mime.multipart.ignoreexistingboundaryparameter", false);
		// default to false
		allowEmpty = PropUtil.getBooleanSystemProperty("mail.mime.multipart.allowempty", false);
		// default to true
		bmparse = PropUtil.getBooleanSystemProperty("mail.mime.multipart.bmparse", true);

		if (bmparse) {
			parsebm();
			return;
		}

		InputStream in = null;
		SharedInputStream sin = null;
		long start = 0, end = 0;

		try {
			in = ds.getInputStream();
			if (!(in instanceof ByteArrayInputStream) && !(in instanceof BufferedInputStream) && !(in instanceof SharedInputStream))
				in = new BufferedInputStream(in);
		} catch (Exception ex) {
			throw new MessagingException("No inputstream from datasource", ex);
		}
		if (in instanceof SharedInputStream)
			sin = (SharedInputStream) in;

		ContentType cType = new ContentType(contentType);
		String boundary = null;
		if (!ignoreExistingBoundaryParameter) {
			String bp = cType.getParameter("boundary");
			if (bp != null)
				boundary = "--" + bp;
		}
		if (boundary == null && !ignoreMissingBoundaryParameter && !ignoreExistingBoundaryParameter)
			throw new MessagingException("Missing boundary parameter");

		try {
			// Skip and save the preamble
			LineInputStream lin = new LineInputStream(in);
			StringBuffer preamblesb = null;
			String line;
			String lineSeparator = null;
			while ((line = lin.readLine()) != null) {
				/*
				 * Strip trailing whitespace. Can't use trim method
				 * because it's too aggressive. Some bogus MIME
				 * messages will include control characters in the
				 * boundary string.
				 */
				int i;
				for (i = line.length() - 1; i >= 0; i--) {
					char c = line.charAt(i);
					if (!(c == ' ' || c == '\t'))
						break;
				}
				line = line.substring(0, i + 1);
				if (boundary != null) {
					if (line.equals(boundary))
						break;
					if (line.length() == boundary.length() + 2 && line.startsWith(boundary) && line.endsWith("--")) {
						line = null; // signal end of multipart
						break;
					}
				} else {
					/*
					 * Boundary hasn't been defined, does this line
					 * look like a boundary? If so, assume it is
					 * the boundary and save it.
					 */
					if (line.length() > 2 && line.startsWith("--")) {
						if (line.length() > 4 && line.endsWith("--")) {
							/*
							 * The first boundary-like line we find is
							 * probably *not* the end-of-multipart boundary
							 * line. More likely it's a line full of dashes
							 * in the preamble text. Just keep reading.
							 */
						} else {
							boundary = line;
							break;
						}
					}
				}

				// save the preamble after skipping blank lines
				if (line.length() > 0) {
					// if we haven't figured out what the line separator
					// is, do it now
					if (lineSeparator == null) {
						try {
							lineSeparator = System.getProperty("line.separator", "\n");
						} catch (SecurityException ex) {
							lineSeparator = "\n";
						}
					}
					// accumulate the preamble
					if (preamblesb == null)
						preamblesb = new StringBuffer(line.length() + 2);
					preamblesb.append(line).append(lineSeparator);
				}
			}

			if (preamblesb != null)
				preamble = preamblesb.toString();

			if (line == null) {
				if (allowEmpty)
					return;
				else
					throw new MessagingException("Missing start boundary");
			}

			// save individual boundary bytes for easy comparison later
			byte[] bndbytes = ASCIIUtility.getBytes(boundary);
			int bl = bndbytes.length;

			/*
			 * Read and process body parts until we see the
			 * terminating boundary line (or EOF).
			 */
			boolean done = false;
			getparts:
			while (!done) {
				InternetHeaders headers = null;
				if (sin != null) {
					start = sin.getPosition();
					// skip headers
					while ((line = lin.readLine()) != null && line.length() > 0)
						;
					if (line == null) {
						if (!ignoreMissingEndBoundary)
							throw new MessagingException("missing multipart end boundary");
						// assume there's just a missing end boundary
						complete = false;
						break getparts;
					}
				} else {
					// collect the headers for this body part
					headers = createInternetHeaders(in);
				}

				if (!in.markSupported())
					throw new MessagingException("Stream doesn't support mark");

				ByteArrayOutputStream buf = null;
				// if we don't have a shared input stream, we copy the data
				if (sin == null)
					buf = new ByteArrayOutputStream();
				else
					end = sin.getPosition();
				int b;
				boolean bol = true; // beginning of line flag
				// the two possible end of line characters
				int eol1 = -1, eol2 = -1;

				/*
				 * Read and save the content bytes in buf.
				 */
				for (;;) {
					if (bol) {
						/*
						 * At the beginning of a line, check whether the
						 * next line is a boundary.
						 */
						int i;
						in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
						// read bytes, matching against the boundary
						for (i = 0; i < bl; i++)
							if (in.read() != (bndbytes[i] & 0xff))
								break;
						if (i == bl) {
							// matched the boundary, check for last boundary
							int b2 = in.read();
							if (b2 == '-') {
								if (in.read() == '-') {
									complete = true;
									done = true;
									break; // ignore trailing text
								}
							}
							// skip linear whitespace
							while (b2 == ' ' || b2 == '\t')
								b2 = in.read();
							// check for end of line
							if (b2 == '\n')
								break; // got it! break out of the loop
							if (b2 == '\r') {
								in.mark(1);
								if (in.read() != '\n')
									in.reset();
								break; // got it! break out of the loop
							}
						}
						// failed to match, reset and proceed normally
						in.reset();

						// if this is not the first line, write out the
						// end of line characters from the previous line
						if (buf != null && eol1 != -1) {
							buf.write(eol1);
							if (eol2 != -1)
								buf.write(eol2);
							eol1 = eol2 = -1;
						}
					}

					// read the next byte
					if ((b = in.read()) < 0) {
						if (!ignoreMissingEndBoundary)
							throw new MessagingException("missing multipart end boundary");
						complete = false;
						done = true;
						break;
					}

					/*
					 * If we're at the end of the line, save the eol characters
					 * to be written out before the beginning of the next line.
					 */
					if (b == '\r' || b == '\n') {
						bol = true;
						if (sin != null)
							end = sin.getPosition() - 1;
						eol1 = b;
						if (b == '\r') {
							in.mark(1);
							if ((b = in.read()) == '\n')
								eol2 = b;
							else
								in.reset();
						}
					} else {
						bol = false;
						if (buf != null)
							buf.write(b);
					}
				}

				/*
				 * Create a MimeBody element to represent this body part.
				 */
				MimeBodyPart part;
				if (sin != null)
					part = createMimeBodyPartIs(sin.newStream(start, end));
				else
					part = createMimeBodyPart(headers, buf.toByteArray());
				super.addBodyPart(part);
			}
		} catch (IOException ioex) {
			throw new MessagingException("IO Error", ioex);
		} finally {
			try {
				in.close();
			} catch (IOException cex) {
				// ignore
			}
		}

		parsed = true;
	}

	/**
	 * Parse the InputStream from our DataSource, constructing the
	 * appropriate MimeBodyParts. The <code>parsed</code> flag is
	 * set to true, and if true on entry nothing is done. This
	 * method is called by all other methods that need data for
	 * the body parts, to make sure the data has been parsed.
	 * 
	 * @since JavaMail 1.2
	 */
	/*
	 * Boyer-Moore version of parser. Keep both versions around
	 * until we're sure this new one works.
	 */
	private synchronized void parsebm() throws MessagingException {
		if (parsed)
			return;

		InputStream in = null;
		SharedInputStream sin = null;
		long start = 0, end = 0;

		try {
			in = ds.getInputStream();
			if (!(in instanceof ByteArrayInputStream) && !(in instanceof BufferedInputStream) && !(in instanceof SharedInputStream))
				in = new BufferedInputStream(in);
		} catch (Exception ex) {
			throw new MessagingException("No inputstream from datasource", ex);
		}
		if (in instanceof SharedInputStream)
			sin = (SharedInputStream) in;

		ContentType cType = new ContentType(contentType);
		String boundary = null;
		if (!ignoreExistingBoundaryParameter) {
			String bp = cType.getParameter("boundary");
			if (bp != null)
				boundary = "--" + bp;
		}
		if (boundary == null && !ignoreMissingBoundaryParameter && !ignoreExistingBoundaryParameter)
			throw new MessagingException("Missing boundary parameter");

		try {
			// Skip and save the preamble
			LineInputStream lin = new LineInputStream(in);
			StringBuffer preamblesb = null;
			String line;
			String lineSeparator = null;
			while ((line = lin.readLine()) != null) {
				/*
				 * Strip trailing whitespace. Can't use trim method
				 * because it's too aggressive. Some bogus MIME
				 * messages will include control characters in the
				 * boundary string.
				 */
				int i;
				for (i = line.length() - 1; i >= 0; i--) {
					char c = line.charAt(i);
					if (!(c == ' ' || c == '\t'))
						break;
				}
				line = line.substring(0, i + 1);
				if (boundary != null) {
					if (line.equals(boundary))
						break;
					if (line.length() == boundary.length() + 2 && line.startsWith(boundary) && line.endsWith("--")) {
						line = null; // signal end of multipart
						break;
					}
				} else {
					/*
					 * Boundary hasn't been defined, does this line
					 * look like a boundary? If so, assume it is
					 * the boundary and save it.
					 */
					if (line.length() > 2 && line.startsWith("--")) {
						if (line.length() > 4 && line.endsWith("--")) {
							/*
							 * The first boundary-like line we find is
							 * probably *not* the end-of-multipart boundary
							 * line. More likely it's a line full of dashes
							 * in the preamble text. Just keep reading.
							 */
						} else {
							boundary = line;
							break;
						}
					}
				}

				// save the preamble after skipping blank lines
				if (line.length() > 0) {
					// if we haven't figured out what the line separator
					// is, do it now
					if (lineSeparator == null) {
						try {
							lineSeparator = System.getProperty("line.separator", "\n");
						} catch (SecurityException ex) {
							lineSeparator = "\n";
						}
					}
					// accumulate the preamble
					if (preamblesb == null)
						preamblesb = new StringBuffer(line.length() + 2);
					preamblesb.append(line).append(lineSeparator);
				}
			}

			if (preamblesb != null)
				preamble = preamblesb.toString();

			if (line == null) {
				if (allowEmpty)
					return;
				else
					throw new MessagingException("Missing start boundary");
			}

			// save individual boundary bytes for comparison later
			byte[] bndbytes = ASCIIUtility.getBytes(boundary);
			int bl = bndbytes.length;

			/*
			 * Compile Boyer-Moore parsing tables.
			 */

			// initialize Bad Character Shift table
			int[] bcs = new int[256];
			for (int i = 0; i < bl; i++)
				bcs[bndbytes[i] & 0xff] = i + 1;

			// initialize Good Suffix Shift table
			int[] gss = new int[bl];
			NEXT:
			for (int i = bl; i > 0; i--) {
				int j; // the beginning index of the suffix being considered
				for (j = bl - 1; j >= i; j--) {
					// Testing for good suffix
					if (bndbytes[j] == bndbytes[j - i]) {
						// bndbytes[j..len] is a good suffix
						gss[j - 1] = i;
					} else {
						// No match. The array has already been
						// filled up with correct values before.
						continue NEXT;
					}
				}
				while (j > 0)
					gss[--j] = i;
			}
			gss[bl - 1] = 1;

			/*
			 * Read and process body parts until we see the
			 * terminating boundary line (or EOF).
			 */
			boolean done = false;
			getparts:
			while (!done) {
				InternetHeaders headers = null;
				if (sin != null) {
					start = sin.getPosition();
					// skip headers
					while ((line = lin.readLine()) != null && line.length() > 0)
						;
					if (line == null) {
						if (!ignoreMissingEndBoundary)
							throw new MessagingException("missing multipart end boundary");
						// assume there's just a missing end boundary
						complete = false;
						break getparts;
					}
				} else {
					// collect the headers for this body part
					headers = createInternetHeaders(in);
				}

				if (!in.markSupported())
					throw new MessagingException("Stream doesn't support mark");

				ByteArrayOutputStream buf = null;
				// if we don't have a shared input stream, we copy the data
				if (sin == null)
					buf = new ByteArrayOutputStream();
				else
					end = sin.getPosition();
				int b;

				/*
				 * These buffers contain the bytes we're checking
				 * for a match. inbuf is the current buffer and
				 * previnbuf is the previous buffer. We need the
				 * previous buffer to check that we're preceeded
				 * by an EOL.
				 */
				// XXX - a smarter algorithm would use a sliding window
				// over a larger buffer
				byte[] inbuf = new byte[bl];
				byte[] previnbuf = new byte[bl];
				int inSize = 0; // number of valid bytes in inbuf
				int prevSize = 0; // number of valid bytes in previnbuf
				int eolLen;
				boolean first = true;

				/*
				 * Read and save the content bytes in buf.
				 */
				for (;;) {
					in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
					eolLen = 0;
					inSize = readFully(in, inbuf, 0, bl);
					if (inSize < bl) {
						// hit EOF
						if (!ignoreMissingEndBoundary)
							throw new MessagingException("missing multipart end boundary");
						if (sin != null)
							end = sin.getPosition();
						complete = false;
						done = true;
						break;
					}
					// check whether inbuf contains a boundary string
					int i;
					for (i = bl - 1; i >= 0; i--) {
						if (inbuf[i] != bndbytes[i])
							break;
					}
					if (i < 0) { // matched all bytes
						eolLen = 0;
						if (!first) {
							// working backwards, find out if we were preceeded
							// by an EOL, and if so find its length
							b = previnbuf[prevSize - 1];
							if (b == '\r' || b == '\n') {
								eolLen = 1;
								if (b == '\n' && prevSize >= 2) {
									b = previnbuf[prevSize - 2];
									if (b == '\r')
										eolLen = 2;
								}
							}
						}
						if (first || eolLen > 0) { // yes, preceed by EOL
							if (sin != null) {
								// update "end", in case this really is
								// a valid boundary
								end = sin.getPosition() - bl - eolLen;
							}
							// matched the boundary, check for last boundary
							int b2 = in.read();
							if (b2 == '-') {
								if (in.read() == '-') {
									complete = true;
									done = true;
									break; // ignore trailing text
								}
							}
							// skip linear whitespace
							while (b2 == ' ' || b2 == '\t')
								b2 = in.read();
							// check for end of line
							if (b2 == '\n')
								break; // got it! break out of the loop
							if (b2 == '\r') {
								in.mark(1);
								if (in.read() != '\n')
									in.reset();
								break; // got it! break out of the loop
							}
						}
						i = 0;
					}

					/*
					 * Get here if boundary didn't match,
					 * wasn't preceeded by EOL, or wasn't
					 * followed by whitespace or EOL.
					 */

					// compute how many bytes we can skip
					int skip = Math.max(i + 1 - bcs[inbuf[i] & 0x7f], gss[i]);
					// want to keep at least two characters
					if (skip < 2) {
						// only skipping one byte, save one byte
						// from previous buffer as well
						// first, write out bytes we're done with
						if (sin == null && prevSize > 1)
							buf.write(previnbuf, 0, prevSize - 1);
						in.reset();
						skipFully(in, 1);
						if (prevSize >= 1) { // is there a byte to save?
							// yes, save one from previous and one from current
							previnbuf[0] = previnbuf[prevSize - 1];
							previnbuf[1] = inbuf[0];
							prevSize = 2;
						} else {
							// no previous bytes to save, can only save current
							previnbuf[0] = inbuf[0];
							prevSize = 1;
						}
					} else {
						// first, write out data from previous buffer before
						// we dump it
						if (prevSize > 0 && sin == null)
							buf.write(previnbuf, 0, prevSize);
						// all the bytes we're skipping are saved in previnbuf
						prevSize = skip;
						in.reset();
						skipFully(in, prevSize);
						// swap buffers
						byte[] tmp = inbuf;
						inbuf = previnbuf;
						previnbuf = tmp;
					}
					first = false;
				}

				/*
				 * Create a MimeBody element to represent this body part.
				 */
				MimeBodyPart part;
				if (sin != null) {
					part = createMimeBodyPartIs(sin.newStream(start, end));
				} else {
					// write out data from previous buffer, not including EOL
					if (prevSize - eolLen > 0)
						buf.write(previnbuf, 0, prevSize - eolLen);
					// if we didn't find a trailing boundary,
					// the current buffer has data we need too
					if (!complete && inSize > 0)
						buf.write(inbuf, 0, inSize);
					part = createMimeBodyPart(headers, buf.toByteArray());
				}
				super.addBodyPart(part);
			}
		} catch (IOException ioex) {
			throw new MessagingException("IO Error", ioex);
		} finally {
			try {
				in.close();
			} catch (IOException cex) {
				// ignore
			}
		}

		parsed = true;
	}

	/**
	 * Read data from the input stream to fill the buffer starting
	 * at the specified offset with the specified number of bytes.
	 * If len is zero, return zero. If at EOF, return -1. Otherwise,
	 * return the number of bytes read. Call the read method on the
	 * input stream as many times as necessary to read len bytes.
	 * 
	 * @param in
	 *            InputStream to read from
	 * @param buf
	 *            buffer to read into
	 * @param off
	 *            offset in the buffer for first byte
	 * @param len
	 *            number of bytes to read
	 * @return -1 on EOF, otherwise number of bytes read
	 * @exception IOException
	 *                on I/O errors
	 */
	private static int readFully(InputStream in, byte[] buf, int off, int len) throws IOException {
		if (len == 0)
			return 0;
		int total = 0;
		while (len > 0) {
			int bsize = in.read(buf, off, len);
			if (bsize <= 0) // should never be zero
				break;
			off += bsize;
			total += bsize;
			len -= bsize;
		}
		return total > 0 ? total : -1;
	}

	/**
	 * Skip the specified number of bytes, repeatedly calling
	 * the skip method as necessary.
	 */
	private void skipFully(InputStream in, long offset) throws IOException {
		while (offset > 0) {
			long cur = in.skip(offset);
			if (cur <= 0)
				throw new EOFException("can't skip");
			offset -= cur;
		}
	}

	/**
	 * Create and return an InternetHeaders object that loads the
	 * headers from the given InputStream. Subclasses can override
	 * this method to return a subclass of InternetHeaders, if
	 * necessary. This implementation simply constructs and returns
	 * an InternetHeaders object.
	 * 
	 * @param is
	 *            the InputStream to read the headers from
	 * @exception MessagingException
	 * @since JavaMail 1.2
	 */
	protected InternetHeaders createInternetHeaders(InputStream is) throws MessagingException {
		return new InternetHeaders(is);
	}

	/**
	 * Create and return a MimeBodyPart object to represent a
	 * body part parsed from the InputStream. Subclasses can override
	 * this method to return a subclass of MimeBodyPart, if
	 * necessary. This implementation simply constructs and returns
	 * a MimeBodyPart object.
	 * 
	 * @param headers
	 *            the headers for the body part
	 * @param content
	 *            the content of the body part
	 * @exception MessagingException
	 * @since JavaMail 1.2
	 */
	protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] content) throws MessagingException {
		return new MimeBodyPart(headers, content);
	}

	/**
	 * Create and return a MimeBodyPart object to represent a
	 * body part parsed from the InputStream. Subclasses can override
	 * this method to return a subclass of MimeBodyPart, if
	 * necessary. This implementation simply constructs and returns
	 * a MimeBodyPart object.
	 * 
	 * @param is
	 *            InputStream containing the body part
	 * @exception MessagingException
	 * @since JavaMail 1.2
	 */
	protected MimeBodyPart createMimeBodyPart(InputStream is) throws MessagingException {
		return new MimeBodyPart(is);
	}

	private MimeBodyPart createMimeBodyPartIs(InputStream is) throws MessagingException {
		try {
			return createMimeBodyPart(is);
		} finally {
			try {
				is.close();
			} catch (IOException ex) {
				// ignore it
			}
		}
	}
}
